/***
 *
 *  ipv4/arp.h - Adress Resolution Protocol for RTnet
 *
 *  Copyright (C) 2002 Ulrich Marx <marx@kammer.uni-hannover.de>
 *                2004 Jan Kiszka <jan.kiszka@web.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include <rtdev.h>
#include <stack_mgr.h>
#include <ipv4/arp.h>

#ifdef CONFIG_XENO_DRIVERS_NET_ADDON_PROXY_ARP
#include <ipv4/ip_input.h>
#endif /* CONFIG_XENO_DRIVERS_NET_ADDON_PROXY_ARP */

/***
 *  arp_send:   Create and send an arp packet. If (dest_hw == NULL),
 *              we create a broadcast message.
 */
void rt_arp_send(int type, int ptype, u32 dest_ip, struct rtnet_device *rtdev,
		 u32 src_ip, unsigned char *dest_hw, unsigned char *src_hw,
		 unsigned char *target_hw)
{
	struct rtskb *skb;
	struct arphdr *arp;
	unsigned char *arp_ptr;

	if (rtdev->flags & IFF_NOARP)
		return;

	if (!(skb = alloc_rtskb(sizeof(struct arphdr) +
					2 * (rtdev->addr_len + 4) +
					rtdev->hard_header_len + 15,
				&global_pool)))
		return;

	rtskb_reserve(skb, (rtdev->hard_header_len + 15) & ~15);

	skb->nh.raw = skb->data;
	arp = (struct arphdr *)rtskb_put(
		skb, sizeof(struct arphdr) + 2 * (rtdev->addr_len + 4));

	skb->rtdev = rtdev;
	skb->protocol = __constant_htons(ETH_P_ARP);
	skb->priority = RT_ARP_SKB_PRIO;
	if (src_hw == NULL)
		src_hw = rtdev->dev_addr;
	if (dest_hw == NULL)
		dest_hw = rtdev->broadcast;

	/*
     *  Fill the device header for the ARP frame
     */
	if (rtdev->hard_header &&
	    (rtdev->hard_header(skb, rtdev, ptype, dest_hw, src_hw, skb->len) <
	     0))
		goto out;

	arp->ar_hrd = htons(rtdev->type);
	arp->ar_pro = __constant_htons(ETH_P_IP);
	arp->ar_hln = rtdev->addr_len;
	arp->ar_pln = 4;
	arp->ar_op = htons(type);

	arp_ptr = (unsigned char *)(arp + 1);

	memcpy(arp_ptr, src_hw, rtdev->addr_len);
	arp_ptr += rtdev->addr_len;

	memcpy(arp_ptr, &src_ip, 4);
	arp_ptr += 4;

	if (target_hw != NULL)
		memcpy(arp_ptr, target_hw, rtdev->addr_len);
	else
		memset(arp_ptr, 0, rtdev->addr_len);
	arp_ptr += rtdev->addr_len;

	memcpy(arp_ptr, &dest_ip, 4);

	/* send the frame */
	rtdev_xmit(skb);

	return;

out:
	kfree_rtskb(skb);
}

/***
 *  arp_rcv:    Receive an arp request by the device layer.
 */
int rt_arp_rcv(struct rtskb *skb, struct rtpacket_type *pt)
{
	struct rtnet_device *rtdev = skb->rtdev;
	struct arphdr *arp = skb->nh.arph;
	unsigned char *arp_ptr = (unsigned char *)(arp + 1);
	unsigned char *sha;
	u32 sip, tip;
	u16 dev_type = rtdev->type;

	/*
     *  The hardware length of the packet should match the hardware length
     *  of the device.  Similarly, the hardware types should match.  The
     *  device should be ARP-able.  Also, if pln is not 4, then the lookup
     *  is not from an IP number.  We can't currently handle this, so toss
     *  it.
     */
	if ((arp->ar_hln != rtdev->addr_len) || (rtdev->flags & IFF_NOARP) ||
	    (skb->pkt_type == PACKET_OTHERHOST) ||
	    (skb->pkt_type == PACKET_LOOPBACK) || (arp->ar_pln != 4))
		goto out;

	switch (dev_type) {
	default:
		if ((arp->ar_pro != __constant_htons(ETH_P_IP)) &&
		    (htons(dev_type) != arp->ar_hrd))
			goto out;
		break;
	case ARPHRD_ETHER:
		/*
	     * ETHERNET devices will accept ARP hardware types of either
	     * 1 (Ethernet) or 6 (IEEE 802.2).
	     */
		if ((arp->ar_hrd != __constant_htons(ARPHRD_ETHER)) &&
		    (arp->ar_hrd != __constant_htons(ARPHRD_IEEE802))) {
			goto out;
		}
		if (arp->ar_pro != __constant_htons(ETH_P_IP)) {
			goto out;
		}
		break;
	}

	/* Understand only these message types */
	if ((arp->ar_op != __constant_htons(ARPOP_REPLY)) &&
	    (arp->ar_op != __constant_htons(ARPOP_REQUEST)))
		goto out;

	/*
     *  Extract fields
     */
	sha = arp_ptr;
	arp_ptr += rtdev->addr_len;
	memcpy(&sip, arp_ptr, 4);

	arp_ptr += 4;
	arp_ptr += rtdev->addr_len;
	memcpy(&tip, arp_ptr, 4);

	/* process only requests/replies directed to us */
	if (tip == rtdev->local_ip) {
		rt_ip_route_add_host(sip, sha, rtdev);

#ifdef CONFIG_XENO_DRIVERS_NET_ADDON_PROXY_ARP
		if (!rt_ip_fallback_handler)
#endif /* CONFIG_XENO_DRIVERS_NET_ADDON_PROXY_ARP */
			if (arp->ar_op == __constant_htons(ARPOP_REQUEST)) {
				rt_arp_send(ARPOP_REPLY, ETH_P_ARP, sip, rtdev,
					    tip, sha, rtdev->dev_addr, sha);
				goto out1;
			}
	}

out:
#ifdef CONFIG_XENO_DRIVERS_NET_ADDON_PROXY_ARP
	if (rt_ip_fallback_handler) {
		rt_ip_fallback_handler(skb);
		return 0;
	}
#endif /* CONFIG_XENO_DRIVERS_NET_ADDON_PROXY_ARP */
out1:
	kfree_rtskb(skb);
	return 0;
}

static struct rtpacket_type arp_packet_type = {
	type: __constant_htons(ETH_P_ARP),
	handler: &rt_arp_rcv
};

/***
 *  rt_arp_init
 */
void __init rt_arp_init(void)
{
	rtdev_add_pack(&arp_packet_type);
}

/***
 *  rt_arp_release
 */
void rt_arp_release(void)
{
	rtdev_remove_pack(&arp_packet_type);
}
